package main;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.lang.Thread.State;

class HTTPRequestHandler implements Runnable {
	final static String CRLF = "\r\n";
	private static String requestedName;
	private static Lock lock = new ReentrantLock();
	
	private String serverLine = "Server: Simple Java Http Server";

	Socket socket;
	InputStream input;
	OutputStream output;
	BufferedReader br;
	FileUtil fileUtil;
	UserInputListener listener;
	Thread listenerThread;

	public HTTPRequestHandler(Socket socket, FileUtil fileUtil,
			UserInputListener listener, Thread listenerThread) throws Exception {
		this.socket = socket;
		this.input = socket.getInputStream();
		this.output = socket.getOutputStream();
		this.fileUtil = fileUtil;
		this.listener = listener;
		this.listenerThread = listenerThread;
		this.br = new BufferedReader(new InputStreamReader(
				socket.getInputStream()));
	}

	public void run() {
		try {
			processRequest();
		} catch (Exception e) {
			System.out.println(e);
		}
	}

	/**
	 * Returns text from "targetString", that is between "after" and "before"
	 * 
	 * @param after
	 *            after what sequence of characters text would be taken.
	 * @param before
	 *            before what sequence of characters text would be taken
	 * @param targetString
	 *            String from which text will be extracted
	 * 
	 * @return Returns text from "targetString", that is between "after" and
	 *         "before" Example:
	 *         getTextBetween("name=","&ip=","name=bob&ip=10.10.10.10" will
	 *         return "bob" Because "bob" is between "name=" and "&ip="
	 * */
	private String getTextBetween(String after, String before,
			String targetString) {

		// String.split returns array
		String[] tmp = targetString.split(after); // tmp[0] holds everything
													// that was before
													// occurrence of "after"
													// tmp[1] holds everything
													// that was after occurrence
													// of "after"

		String[] tmp2 = tmp[1].split(before); // tmp2[0] holds everything that
												// was before occurrence of
												// "before"
												// tmp2[1] holds everything that
												// was after occurrence of
												// "before"
		// therefore we return tmp2[0]
		return tmp2[0];
	}

	private void processRequest() throws Exception {
		while (true) {
			// get header
			String headerLine = br.readLine();
			// System.out.println(headerLine);
			if (headerLine.equals(CRLF) || headerLine.equals("")) {
				break;
			}

			StringTokenizer s = new StringTokenizer(headerLine);
			String temp = s.nextToken();

			if (temp.equals("GET")) {
				String URI = s.nextToken();
				String action = URI;

				// process request depending on the URI:
				if (action.startsWith("/chat/asknames")) {
					processRequestAsknames();
				} else if (action.startsWith("/chat/sendmessage")) {
					processRequestSendmessage(action);
				} else if (action.startsWith("/chat/sendname")) {
					processRequestSendname(action);
				} else if (action.startsWith("/chat/findname")) {
					processRequestFindname(action);
				} else {
					outputHttp404();
				}
			}
		}

		try {
			output.close();
			br.close();
			socket.close();
		} catch (Exception e) {
		}
	}

	private void processRequestFindname(String action) throws IOException {
		outputHttp200();

		// http://11.22.33.44:PORT/chat/findname?name=NIMI&myip=22.22.33.44:6666&ttl=TIMETOLIVE

		String recievedNameToCheck = this.getTextBetween("name=",
				"&myip", action);
		String addressOfSender = this.getTextBetween("myip=",
				"&ttl", action);
		String result = this.fileUtil
				.findRecordLine(recievedNameToCheck);
		String record = "[\"" + "Unknown" + "\",\""
				+ addressOfSender + "\"]";
		//if we have an answer we should respond
		if (!result.equals("Not found")) {
			listener.sendNameRequest(record, result);
		}
		// else Does nothing
	}

	private void processRequestSendname(String action) throws IOException,
			InterruptedException {
		// http://11.22.33.44:PORT/chat/sendname?name=NIMI&ip=22.22.33.44:6666&ttl=TIMETOLIVE
		outputHttp200();

		String recievedName = this.getTextBetween("name=", "&ip",
				action);
		String recievedIp_port = this.getTextBetween("ip=", "&ttl",
				action);
		String record = "[\"" + recievedName + "\",\""
				+ recievedIp_port + "\"]";
		this.fileUtil.appendLine(record);
		if (recievedName.equals(listener.getDesiredName())) {
			//if name is found by more than one Handler, we still need only one answer:
			if (lock.tryLock()) {
				try {
					//waits until listener starts wait(10000);
					synchronized (listener) {
//						System.out.println("receiving answer A");
						listener.setAnswerForDesiredName(record);
						
						listener.notify();
					}
				} finally {
					lock.unlock();
				}
			}
		}
	}

	private void processRequestSendmessage(String action) throws IOException {
		outputHttp200();

		// http://11.22.33.44:PORT/chat/sendmessage?myname=tanel&myip=22.33.44.55:6666&message=asasas&ttl=TIMETOLIVE

		String nameOfSender = this.getTextBetween("myname=",
				"&myip", action);
		String recievedMessage = this.getTextBetween("message=",
				"&ttl", action);
		recievedMessage=URLDecoder.decode(recievedMessage, "UTF-8");
		System.out.println(nameOfSender + " says->: "
				+ recievedMessage);
	}

	private void processRequestAsknames() throws IOException, Exception {
		FileInputStream fis;
		String statusLine;
		String contentTypeLine;
		String contentLengthLine;
		try {
			fis = new FileInputStream("default.json");
			statusLine = "HTTP/1.0 200 OK" + CRLF;
			contentTypeLine = "Content-type: "
					+ contentType("default.json") + CRLF;
			contentLengthLine = "Content-Length: "
					+ (new Integer(fis.available())).toString()
					+ CRLF;
			//output:
			outputHttpHeader(serverLine, statusLine, contentTypeLine, contentLengthLine);
			outputHttpBody(fis);
		} catch (FileNotFoundException e) {
			outputHttp404();
		}
	}
	
	private void outputHttp200() throws IOException {
		String entityBody = "<html><body><p>Request recieved!</p></body></html>";
		String statusLine = "HTTP/1.0 200 OK" + CRLF;
		String contentTypeLine = "Content-type: " + "text/html" + CRLF;
		String contentLengthLine = "Content-Length: "
				+ entityBody.getBytes().length + CRLF;
		
		outputHttpHeader(serverLine, statusLine, contentTypeLine,
				contentLengthLine);
		outputHttpBody(entityBody);
	}
	
	private void outputHttp404() throws IOException {
		String statusLine = "HTTP/1.0 404 Not Found" + CRLF;
		String contentTypeLine = "text/html";
		String entityBody = "<HTML>"
				+ "<HEAD><TITLE>404 Not Found</TITLE></HEAD>"
				+ "<BODY>404 Not Found"
				+ "<br>usage:http://yourHostName:port/"
				+ "fileName.html</BODY></HTML>";
		String contentLengthLine = "Content-Length: "
				+ entityBody.getBytes().length + CRLF;
		outputHttpHeader(serverLine, statusLine, contentTypeLine,
				contentLengthLine);
		outputHttpBody(entityBody);
	}

	private void outputHttpBody(FileInputStream fis) throws Exception, IOException {
		sendBytes(fis, output);
		fis.close();
	}

	private void outputHttpBody(String entityBody) throws IOException {
		output.write(entityBody.getBytes());
	}

	private void outputHttpHeader(String serverLine, String statusLine,
			String contentTypeLine, String contentLengthLine)
			throws IOException {
		output.write(statusLine.getBytes());
		output.write(serverLine.getBytes());
		output.write(contentTypeLine.getBytes());
		output.write(contentLengthLine.getBytes());
		// Send a blank line to indicate the end of the header lines.
		output.write(CRLF.getBytes());
		// System.out.println(CRLF);
	}

	private static void sendBytes(FileInputStream fis, OutputStream os)
			throws Exception {

		byte[] buffer = new byte[1024];
		int bytes = 0;

		while ((bytes = fis.read(buffer)) != -1) {
			os.write(buffer, 0, bytes);
		}
	}

	private static String contentType(String fileName) {
		if (fileName.endsWith(".htm") || fileName.endsWith(".html")
				|| fileName.endsWith(".txt")) {
			return "text/html";
		} else if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) {
			return "image/jpeg";
		} else if (fileName.endsWith(".gif")) {
			return "image/gif";
		} else {
			return "application/octet-stream";
		}
	}
}
